package com.lyy.demo.openapi.knife;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.springdoc.core.customizers.ParameterCustomizer;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;


/**
 * 枚举参数自定义配置类。用于为API枚举参数提供自定义配置，包括参数描述和枚举值的设置.
 *
 * @author lyy
 * @date 2025/1/11 20:13
 */
@Configuration
public class SpringDocEnumPlugin implements ParameterCustomizer, PropertyCustomizer {

    /**
     * 自定义参数中的枚举描述.
     * 适用于 application/x-www-form-urlencoded 提交的请求参数
     *
     * @param parameter       参数>对应参数的文档配置，用于获取或设置参数的描述
     * @param methodParameter 参数>对应方法参数的类型，用于获取枚举
     * @return 参数>自定义后的参数
     */
    @Override
    public Parameter customize(Parameter parameter, MethodParameter methodParameter) {
        if (parameter == null) {
            return null;
        }
        Class<?> clazz = methodParameter.getParameterType();
        enumDescHandler(clazz, (enumDesc, codes) -> {
            String describe =
                    parameter.getDescription() == null ? enumDesc : parameter.getDescription() + "：" + enumDesc;
            parameter.description(describe);
            Schema schema = parameter.getSchema();
            schema.setDescription(describe);
            if (CollUtil.isEmpty(schema.getEnum())) {
                schema.setEnum(codes);
            }
        });
        return parameter;
    }

    /**
     * 自定义属性中的枚举描述.
     * 适用于 application/json 提交的请求参数 及 json返回值
     *
     * @param schema 属性>对应属性的文档配置，用于获取或设置属性的描述
     * @param type   类型>对应属性的类型，用于获取枚举
     * @return 属性>自定义后的属性
     */
    @Override
    public Schema customize(Schema schema, AnnotatedType type) {
        if (schema == null) {
            return null;
        }
        Type typeType = type.getType();
        boolean isSimpleType = typeType instanceof SimpleType;
        if (!isSimpleType) {
            return schema;
        }
        SimpleType simpleType = (SimpleType) typeType;
        Class<?> clazz = simpleType.getRawClass();
        enumDescHandler(clazz, (enumDesc, codes) -> {
            String describe = schema.getDescription() == null ? enumDesc : schema.getDescription() + "：" + enumDesc;
            schema.description(describe);
        });
        return schema;
    }

    /**
     * 处理枚举描述.
     *
     * @param clazz    枚举类
     * @param consumer 处理器
     */
    private void enumDescHandler(Class<?> clazz, BiConsumer<String, List<String>> consumer) {
        if (Enum.class.isAssignableFrom(clazz)) {
            SwaggerDisplayEnum anno = AnnotationUtils.findAnnotation(clazz, SwaggerDisplayEnum.class);
            if (anno != null) {
                List<String> displayDescribeValues = getStringByEnum(anno, clazz, DisplayEnum.CODE_DESC);
                List<String> displayCodes = getStringByEnum(anno, clazz, DisplayEnum.CODE);
                String enumDesc = StrUtil.join(StrUtil.COMMA, displayDescribeValues);
                if (StrUtil.isNotBlank(enumDesc)) {
                    consumer.accept(enumDesc, displayCodes);
                }
            }
        }
    }

    /**
     * 获取枚举展示内容.
     *
     * @param annotation  SwaggerDisplayEnum注解引用
     * @param clazz       枚举类
     * @param displayEnum 展示类型
     * @return 展示内容
     */
    private List<String> getStringByEnum(SwaggerDisplayEnum annotation, Class<?> clazz, DisplayEnum displayEnum) {
        if (annotation == null) {
            return List.of();
        }
        Object[] enumConstants = clazz.getEnumConstants();
        String code = annotation.code();
        String desc = annotation.desc();
        return Arrays.stream(enumConstants)
                .filter(Objects::nonNull)
                .map(item -> {
                    Class<?> currentClass = item.getClass();
                    Field codeField = ReflectionUtils.findField(currentClass, code);
                    String errorMsgTemplate = "注解：@SwaggerDisplayEnum(code = \"{}\", desc = \"{}\")，其中{}在枚举中没有对应的属性{}";
                    Assert.notNull(codeField, errorMsgTemplate, code, desc, "code", code);
                    ReflectionUtils.makeAccessible(codeField);
                    Object codeStr = ReflectionUtils.getField(codeField, item);
                    if (codeStr == null) {
                        return "";
                    }
                    switch (displayEnum) {
                        case CODE_DESC:
                            Field descField = ReflectionUtils.findField(currentClass, desc);
                            Assert.notNull(descField, errorMsgTemplate, code, desc, "desc", desc);
                            ReflectionUtils.makeAccessible(descField);
                            Object descStr = ReflectionUtils.getField(descField, item);
                            // 是字符串类型的并且是数值类型的
                            if (codeStr instanceof String && ((String) codeStr).matches("-?\\d+")) {
                                return StrUtil.format("\"{}\"-{}", codeStr, descStr);
                            }
                            return codeStr + "-" + descStr;
                        case CODE:
                            // 是字符串类型的并且是数值类型的
                            if (codeStr instanceof String && ((String) codeStr).matches("-?\\d+")) {
                                return StrUtil.format("\"{}\"", codeStr);
                            }
                            return codeStr.toString();
                        default:
                            return "";
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * 枚举描述展示类型.
     */
    enum DisplayEnum {
        // 展示code+desc
        CODE_DESC,
        // 展示code
        CODE,
    }
}
